package testcase;

import org.opengauss.util.GT;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import util.DatetimeUtil;

import java.sql.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author zhoucong
 * @ClassName RollbackDemoV3
 * @Description 所有线程一起唤醒，并提交
 * @createTime 2023年07月11日09:51:00
 */
public class RollbackDemoV3 {
    private static String URL = "jdbc:opengauss://113.137.58.68:50019/tpccdb?loggerLevel=OFF&socketTimeoutInConnecting=0&socketTimeout=0";
    private static final String USERNAME = "tpccuser";
    private static final String PASSWORD = "Huawei@123";

    private static int IDLE_CONNECTIONS = 0;

    private static int COMMIT_CONNECTIONS = 0;

    private static int ROLLBACK_CONNECTIONS = 1500;

    private static int INSERT_NUM = 30;

    private static final Logger LOGGER = LoggerFactory.getLogger(RollbackDemoV3.class);

    private static long startTimeStamp;

    private static long timeBeforeRollback = 1000 * 90;

    private static void initTables() throws SQLException {
        Statement statement;
        // 初始化1000个链接
        try (Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD)) {
            statement = connection.createStatement();
            statement.execute("drop schema if exists savepoint_test CASCADE");
            statement.execute("create schema savepoint_test");
            for (int i = 0; i < ROLLBACK_CONNECTIONS; i++) {
                statement.execute("create table if not exists savepoint_test.rollback_t" + i + "(id int, value varchar)");
            }
//            for (int i = 0; i < COMMIT_CONNECTIONS; i++) {
//                statement.execute("create table if not exists savepoint_test.commit_t" + i + "(id int, value varchar)");
//            }
//            for (int i = 0; i < IDLE_CONNECTIONS; i++) {
//                statement.execute("create table if not exists savepoint_test.idle_t" + i + "(id int, value varchar)");
//            }
            statement.close();
        }
        LOGGER.info(GT.tr("init tables complete, idle connections: {0}, commit connections: {1}, " +
                "rollback connections: {2}", IDLE_CONNECTIONS, COMMIT_CONNECTIONS, ROLLBACK_CONNECTIONS));
    }

    private static void executeInsertAndSavepoint(Connection connection, String tableName, int insertNum) throws SQLException {
        PreparedStatement preparedStatement = null;
        for (int i = 0; i < insertNum; i++) {
            try {
                connection.setSavepoint(GT.tr("savepoint{0}_{1}", tableName, String.valueOf(i)));
                preparedStatement = connection.prepareStatement(GT.tr("insert into savepoint_test.{0} values (?,?)", tableName));
                preparedStatement.setInt(1,i);
                preparedStatement.setString(2, "sfsfsfsfsfs");
                preparedStatement.execute();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                assert preparedStatement != null;
                preparedStatement.close();
            }
        }
    }

    private static void initTable(Connection connection, String tableName) throws SQLException {
        Statement statement = null;
        try {
            statement = connection.createStatement();
            statement.execute("create table if not exists savepoint_test." + tableName + "(id int, value varchar)");
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            assert statement != null;
            statement.close();
        }
    }

    private static void executeCommitConnections(int tableId) throws SQLException {
        Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
        try {
            connection.setAutoCommit(false);
            initTable(connection, GT.tr("commit_t{0}", String.valueOf(tableId)));
            executeInsertAndSavepoint(connection, GT.tr("commit_t{0}", String.valueOf(tableId)), INSERT_NUM);
            // 休眠一段时间，保证所有线程一起唤醒
            long endTimeStamp = System.currentTimeMillis();
            long sleepTimeMillSeconds = timeBeforeRollback - endTimeStamp + startTimeStamp;
            LOGGER.info(GT.tr("CommitConnection finish executeInsertAndSavepoint, start to sleep, id: {0}, sleepTimeMillSeconds: {1}" ,
                    String.valueOf(tableId), sleepTimeMillSeconds));
            Thread.sleep(sleepTimeMillSeconds);

            // 计算提交所需时间
            long timeBeforeCommit = System.currentTimeMillis();
            connection.commit();
            long timeAfterCommit = System.currentTimeMillis();
            LOGGER.info(GT.tr("Transaction commit，id: {0}, date: {1}, time interval: {2}", String.valueOf(tableId), DatetimeUtil.getTheTimeInMilliseconds(), timeAfterCommit - timeBeforeCommit));
        } catch (SQLException | InterruptedException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    private static void executeRollbackConnections(int tableId) throws SQLException {
        Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
        try {
            connection.setAutoCommit(false);
            initTable(connection, GT.tr("rollback_t{0}", String.valueOf(tableId)));
            executeInsertAndSavepoint(connection, GT.tr("rollback_t{0}", String.valueOf(tableId)), INSERT_NUM);
            // 休眠一段时间，保证所有线程一起唤醒
            long endTimeStamp = System.currentTimeMillis();
            long sleepTimeMillSeconds = timeBeforeRollback - endTimeStamp + startTimeStamp;
            LOGGER.info(GT.tr("RollbackConnection finish executeInsertAndSavepoint, start to sleep, id: {0}, sleepTimeMillSeconds: {1}" ,
                    String.valueOf(tableId), sleepTimeMillSeconds));
            Thread.sleep(sleepTimeMillSeconds);

            // 计算回滚所花时间
            long startTime = System.currentTimeMillis();
            connection.rollback();
            long endTime = System.currentTimeMillis();
            LOGGER.info(GT.tr("Transaction rollback，id: {0}, date: {1}, time interval: {2}", String.valueOf(tableId), DatetimeUtil.getTheTimeInMilliseconds(), endTime - startTime));
        } catch (SQLException | InterruptedException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    private static void execute() {
        ExecutorService executorService = Executors.newFixedThreadPool(COMMIT_CONNECTIONS + ROLLBACK_CONNECTIONS);
        startTimeStamp = System.currentTimeMillis();
        for (int i = 0; i < COMMIT_CONNECTIONS; i++) {
            int finalI = i;
            executorService.submit(()->{
                LOGGER.info(GT.tr("Start commit thread {0}", finalI));
                try {
                    executeCommitConnections(finalI);
                } catch (SQLException  e) {
                    e.printStackTrace();
                    throw new RuntimeException(e);
                }
            });
        }

        for (int i = 0; i< ROLLBACK_CONNECTIONS; i++) {
            int finalI = i;
            executorService.submit(()->{
                LOGGER.info(GT.tr("Start rollback thread {0}", finalI));
                try {
                    executeRollbackConnections(finalI);
                } catch (SQLException  e) {
                    e.printStackTrace();
                    throw new RuntimeException(e);
                }
            });
        }
    }

    public static void main(String[] args) throws SQLException {
        LOGGER.info(GT.tr("required params : URL INSERT_NUM(savepoint num) timeBeforeRollback(seconds) " +
                "COMMIT_CONNECTIONS ROLLBACK_CONNECTIONS"));
        if (args.length >=1 ) {
            URL = args[0];
        }
        if (args.length >=2) {
            INSERT_NUM = Integer.parseInt(args[1]);
        }
        if (args.length >=3) {
            timeBeforeRollback = Integer.parseInt(args[2]) * 1000;
        }
        if (args.length >=4) {
            COMMIT_CONNECTIONS = Integer.parseInt(args[3]);
        }
        if (args.length >=5) {
            ROLLBACK_CONNECTIONS = Integer.parseInt(args[4]);
        }
        LOGGER.info(GT.tr("Start new thread {0}"));
        initTables();
        execute();
    }
}
